<body>
  <div id="app">
    <h1>{{song}}</h1>
    <h2>《{{album.name}}》是{{singer}}</h2>
    <h3>主打歌为{{album.theme}}</h3>
    <p>作词人为{{singer}}等人</p>
    <p>为你弹奏肖邦的{{album.theme}}</p>
    <p>创建时间{{album.time.createTime}}发行的专辑</p>
    <p>求和的值是{{sum}}</p>
    <p>现在时间：{{getFormatData}}</p>
    <input type="number" v-model="a">
    <input v-model="song" type="text">
  </div>
</body>
<script>
  // 发布订阅模式订阅和发布 
  function EventHub() {
    this.events = { watcher: [] }; // 一个数组(存放函数的事件池)
    console.log(this.events);
    
  }
  EventHub.prototype.on = function (eventName, fn) {
    if (!this.events[eventName]) { this.events[eventName] = [] }
    this.events[eventName].push(fn);
  }
  EventHub.prototype.emit = function (eventName, data) {
    if (!this.events[eventName]) { return }
    this.events[eventName].forEach(callbackItem => {
      if (typeof (callbackItem) == "function") {
        callbackItem().call(null, data)
      }
      if (typeof (callbackItem) != "function") {
        Object.getOwnPropertyNames(callbackItem.__proto__).forEach(item => {
          if (item !== "constructor") {
            callbackItem[item]()
          }
        })
      }
    }); // 绑定的方法，都有一个update方法
  };
  //销毁单个例子
  EventHub.prototype.off = function (eventName) {
    this.events[eventName] = null
  }
  //销毁的方法
  EventHub.prototype.destroy = function () {
    this.events = null
  }
</script>
<script>
  // 创建一个Vmvvm构造函数,这里用es6方法将options赋一个初始值，防止没传，等同于options || {}
  function Vmvvm(options = {}) {
    let { ...$args } = options
    this.$options = options; // 将所有属性挂载到了$options ,vm.$options Vue上是将所有属性挂载到上面
    // 数据代理就是让我们每次拿data里的数据时，不用每次都写一长串
    //vm._data.a.b -> vm.a.b,this[key]=xxxxx
    Object.keys($args).forEach(item => {
      this['_' + item] = this.$options[item]  //把私有_开头的项对应this.$options的每一项
      if (item == 'data') { observer(this._data) }// 数据劫持
      if (!['mounted'].includes(this['_' + item])) {
        for (let key in $args[item]) {
          Object.defineProperty(this, key, {
            configurable: true,
            enumerable: true,
            get() { return this['_' + item][key] },
            set(newVal) { this['_' + item][key] = newVal }
          });
        }
      }
    })
    initComputed.call(this);         //计算函数,初始化computed,将this指向实例
    new Compile(options.el, this);   //编译
    $args.mounted.call(this);       // 所有事情处理好后执行mounted钩子函数
  }
  //--------------------------------------------------------------------------
  // 递归数据劫持
  function observer(data) {
    if (!data || typeof data !== 'object') return;// 防止递归溢出
    return Observer.Singleton(data);
    // return new Observer(data)
  }
  // 单例模式,使用了闭包模式
  Observer.Singleton = (function () {
    var instance = null;
    return function (data) {
      if (!instance) { instance = new Observer(data) }
      return instance;
    }
  })();
  
  // let eventHub = new EventHub();
  // 创建一个Observer构造函数,写数据劫持的主要逻辑
  function Observer(data) {
    // 所谓数据劫持就是给对象增加get,set，先遍历一遍对象再说
    let eventHub = new EventHub();
    for (let key in data) {     // 把data属性通过defineProperty的方式定义属性
      let val = data[key];
      observer(val);   // 递归继续向下找，实现深度的数据劫持
      Object.defineProperty(data, key, {
        configurable: true,
        get() {
          EventHub.target && eventHub.on('watcher', EventHub.target); // 将watcher添加到订阅事件中 [watcher]  
          return val
        },
        set(newVal) {
          if (newVal === val) { return } // 设置的值和以前值一样就不理它
          val = newVal;       // 如果以后再获取值(get)的时候，将刚才设置的值再返回去
          observer(newVal);    // 当设置为新值后，也需要把新值再去定义成属性
          eventHub.emit('watcher', "👌");   // 让所有watcher的update方法执行即可
        }
      });
    }
  }
  //--------------------------------------------------------------------------
  // 监听函数，通过Watcher这个类创建的实例，都拥有update方法
  // 传进的newVal => { node.textContent = txt.replace(reg, newVal).trim();
  function Watcher(vm, matchString, fn) {
    //参考:let instance = Axios.prototype.request.bind(context)
    //增加EventHub的静态方法EventHub.target，相当于EventHub.target = new Watcher()
    //或者相当于EventHub.target = new Watcher().__p
    EventHub.target = this;
    this.fn = fn;
    this.vm = vm;   //把传入的指向存起来
    this.matchString = matchString;
    this.getChainReactionVal;  //获得对象链式后的值
    matchString.split('.').forEach(item => {
      this.getChainReactionVal = this.vm[item];     // 获取到this.a.b，默认就会调用get方法
    });
    console.log("EventHub.target",EventHub.target);
    EventHub.target = null;
  }
  Watcher.prototype.update = function () {
    this.getChainReactionVal;  //获得对象链式后的值
    this.matchString.split('.').forEach(key => {
      this.getChainReactionVal = this.vm[key];    // 对象链式获取了this[xxx][xxx][xxx]获取新值
    });
    this.fn(this.getChainReactionVal);   // 将每次拿到的新值去替换{{}}的内容即可
  };
  function initComputed() {
    let computed = this.$options.computed;  // 从options上拿到computed属性   {sum: ƒ, noop: ƒ}
    // 得到的都是对象的key可以通过Object.keys转化为数组
    Object.keys(computed).forEach(key => {  // key就是对象computed的参数
      Object.defineProperty(this, key, {
        // 这里判断是computed里的key是对象还是函数，要是函数直接调get(),要是对象的话，手动调一下get方法即可
        // 如： sum() {return this.a + this.b;},他们获取a和b的值就会调用get方法,所以不需要new Watcher去监听变化了
        get: typeof computed[key] === 'function' ? computed[key] : computed[key].get,
        set() { }
      });
    });
  }
  // 创建Compile构造函数
  function Compile(el, vm) {
    vm.$el = document.querySelector(el);            // 将el挂载到实例上方便调用,在el范围里将内容都拿到，当然不能一个一个的拿
    let fragment = document.createDocumentFragment();      // 可以选择移到内存中去然后放入文档碎片中，节省开销
    while (child = vm.$el.firstChild) { fragment.appendChild(child) } // 此时将el中的内容放入内存中
    replace(fragment.childNodes);                      // 对el里面的内容进行替换
    vm.$el.appendChild(fragment);                      // 再将文档碎片放入el中
    function replace(frag) {
      //将虚伪数组转真数组后遍历
      Array.from(frag)
        .forEach(node => {
          // 如果还有子节点，继续递归replace
          if (node.childNodes && node.childNodes.length) { replace(node.childNodes) }
          let txt = node.textContent;
          let reg = /\{\{(.*?)\}\}/g;   // 正则匹配{{}}
          let val = vm;
          if (node.nodeType === 3 && reg.test(txt)) { replaceTxt() } // 把有{{}}替换成数据后渲染
          if (node.nodeType === 1) { replaceVmodel() }  // 替换双向数据绑定
          function replaceVmodel() {
            let nodeAttr = node.attributes; // 获取dom上的所有属性,是个类数组
            Array.from(nodeAttr).forEach(item => {
              let name = item.name;            // v-model  type
              let matchString = item.value;    // text
              if (name.includes('v-model')) {
                node.value = vm[matchString];   // this.c 为 2
              }
              // 监听变化
              new Watcher(vm, matchString, function (newVal) {
                node.value = newVal;   // 当 watcher 触发时会自动将内容放进输入框中
              });
              node.addEventListener('input', e => {
                let newVal = e.target.value;
                // 相当于给this.c赋了一个新值
                // 而值的改变会调用set，set中又会调用notify，notify中调用 watcher 的update方法实现了更新
                vm[matchString] = newVal;
              });
            });
          }
          function replaceTxt() {
            let matchString;
            function matchedFn() {
              node.textContent = txt.replace(reg, (matched, placeholder) => {
                matchString = placeholder
                return placeholder.split('.').reduce((val, key) => { return val[key]; }, vm);
              });
            }
            matchedFn()
            if (matchString) {
              new Watcher(vm, matchString, matchedFn);   // 监听变化，进行匹配替换内容
            }
          }
          function replaceTxt2() {  //有bug
            // console.log(RegExp.$1); // 匹配到的第一个分组 如： a.b, c
            RegExp.$1.split('.').forEach(key => { val = val[key] }); // 如this.a.b
            node.textContent = txt.replace(reg, val).trim();  // 用trim方法去除一下首尾空格
            // 监听变化，给Watcher再添加两个参数，用来取新的值(newVal)给回调函数传参
            new Watcher(vm, RegExp.$1, newVal => { node.textContent = txt.replace(reg, newVal).trim(); });
          }
        });
    }
  }
</script>
<script>
  let vm = new Vmvvm({
    el: '#app',
    data: {
      song: '发如雪',
      album: {
        name: '十一月的萧邦',
        theme: '夜曲',
        time: {
          createTime: '2019-02-27',
          updateTime: '2019-02-28',
          datenow: formatDate(Date.now())
        }
      },
      singer: '周杰伦',
      a: 1,
      b: 2,
    },
    method: {
      sing() {
        this.a = 10;
        console.log("我送你离开千里之外")
      },
      write() {
        console.log("这首歌是别人写给我的")
      },
      getTimeNow() {
        setInterval(() => {
          this.album.time.datenow = formatDate(Date.now() + 1)
        }, 1000);
        setTimeout(() => {
          this.album.name="龙的传人"
        }, 1000);
      }
    },
    computed: {
      sum() {
        return parseInt(this.a) + parseInt(this.b)
      },
      getFormatData() {
        return formatDate(this.album.time.datenow)
      }
    },
    mounted() {
      this.sing()
      this.write()
      this.getTimeNow()
      console.log(this);
    }
  });
  function formatDate(time = 1581933784635, patter = "YY-MM-DD", ) {
    if (!time) return "时间显示错误";
    if (toString.call(time) === "[object Date]") {
      time = new Date(time)
    } else if (typeof time == "number") {
      time = new Date(time)
    } else {
      time = new Date(time)
    }
    let YY = time.getFullYear();
    let MM = time.getMonth() + 1;
    let DD = time.getDate();
    MM = MM > 9 ? MM : "0" + MM;
    DD = DD > 9 ? DD : "0" + DD;
    if (patter && patter.toLowerCase() === "YY-MM-DD") {
      return `${YY}-${MM}-${DD}`;
    } else {
      let hh = time
        .getHours()
        .toString()
        .padStart("2", 0);
      let mm = time
        .getMinutes()
        .toString()
        .padStart("2", 0);
      let ss = time
        .getSeconds()
        .toString()
        .padStart("2", 0);
      return `${YY}-${MM}-${DD} ${hh}:${mm}:${ss}`;
    }
  }
</script>
<!---------------------- 这里的代码不参与执行 ---------------------->
<script>
  //创建单例
  //惰性单例的精髓,单例模式抽象，分离创建对象的函数和判断对象是否已经创建
  //形参fn是我们的构造函数，我们只要传入任何自己需要的构造函数，就能生成一个新的惰性单例
  function getSingle(fn) {
    var result;
    return function () {
      return result || (result = fn.apply(this, arguments));
    }
  };
  // 单例模式,使用了闭包模式
  window.Singleton = (function () {
    var instance = null;
    return function (data) {
      if (!instance) { instance = new Observer(data) }
      return instance;
    }
  })();
</script>